/*
 * AnimationSample.cs
 * Author: Bruno Evangelista
 * Copyright (c) 2008 Bruno Evangelista. All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XNAnimation;
using XNAnimation.Controllers;
using XNAnimation.Effects;

namespace CrossFadeBlendSample
{
    /// <summary>
    /// This sample ilustrates how to use an animation controller
    /// </summary>
    public class XNAnimationSample : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;

        /// <summary>
        /// Graphics 2D stuff
        /// </summary>
        SpriteBatch spriteBatch;
        SpriteFont spriteFont;

        /// <summary>
        /// View and Projection camera matrices 
        /// </summary>
        Matrix[] absoluteBoneTransforms;
        Matrix cameraView;
        Matrix cameraProjection;

        /// <summary>
        /// Stores the last keyboard state
        /// </summary>
        KeyboardState lastKeyboradState;

        /// <summary>
        /// The SkinnedModel class handles skeletal animated models
        /// </summary>
        SkinnedModel skinnedModel;

        /// <summary>
        /// Index of the active skinned model being drawed
        /// </summary>
        int activeSkinnedModelIndex = 0;

        /// <summary>
        /// An array with all the skinned model files available
        /// </summary>
        readonly String[] skinnedModelFiles = { "Models/PlayerMarine", "Models/EnemyBeast",
            "Models/Tiny"};

        /// <summary>
        /// Custom world transformation for each model
        /// </summary>
        readonly Matrix[] worldTransformations = {Matrix.CreateTranslation(0, -12, 0), 
            Matrix.Identity, Matrix.Identity};

        /// <summary>
        /// The AnimationController class handles the animation playback
        /// </summary>
        AnimationController animationController;
        
        /// <summary>
        /// Index of the active animation being played
        /// </summary>
        int activeAnimationClipIndex = 0;

        /// <summary>
        /// If true, enable the keyframe interpolation
        /// </summary>
        bool enableInterpolation = false;

        /// <summary>
        /// Copyright messages that appears when each model is draw
        /// </summary>
        readonly String[] copyrightMessages = { 
            "Model courtesy of FloatBox Studios", 
            "Model courtesy of Psionic",
            "DirectX SDK Sample Model"};

        public XNAnimationSample()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferMultiSampling = true;

            Content.RootDirectory = "Content";
        }

        private void LoadSkinnedModel()
        {
            // Loads an animated model
            skinnedModel = Content.Load<SkinnedModel>(skinnedModelFiles[activeSkinnedModelIndex]);

            // Copy the absolute transformation of each node
            absoluteBoneTransforms = new Matrix[skinnedModel.SkeletonBones.Count];
//            skinnedModel.Model.CopyBoneTransformsTo(absoluteBoneTransforms);

            // Creates an animation controller
            animationController = new AnimationController(skinnedModel.SkeletonBones);

            // Start the first animation stored in the AnimationClips dictionary
            animationController.StartClip(
                skinnedModel.AnimationClips.Values[activeAnimationClipIndex]);
        }

        protected override void LoadContent()
        {
            // Create a SpriteBatch and a SpritFont for 2D drawing
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Arial");

            // Setup camera
            cameraView = Matrix.CreateLookAt(new Vector3(10, 15, 30), new Vector3(0, 2, 0), Vector3.Up);
            cameraProjection = Matrix.CreatePerspectiveFieldOfView(1, 1, 1, 1000);

            // Load a skinned model
            activeSkinnedModelIndex = 0;
            activeAnimationClipIndex = 0;

            LoadSkinnedModel();
        }


        protected override void Update(GameTime gameTime)
        {
            #region Keyboard Handling
            KeyboardState keyboradState = Keyboard.GetState();

            // Exit
            if (keyboradState.IsKeyDown(Keys.Escape))
                Exit();

            // 'Up' and 'Down' changes the animation speed
            if (keyboradState.IsKeyDown(Keys.Up))
            {
                animationController.Speed += 0.005f;
            }
            else if (keyboradState.IsKeyDown(Keys.Down))
            {
                animationController.Speed = (animationController.Speed < 0.1f) ?
                    0.1f : animationController.Speed - 0.005f;
            }

            // 'Left' and 'Right loop through the animations
            if (keyboradState.IsKeyDown(Keys.Left) && lastKeyboradState.IsKeyUp(Keys.Left))
            {
                activeAnimationClipIndex = ((activeAnimationClipIndex - 1) < 0)?
                    skinnedModel.AnimationClips.Count - 1 : activeAnimationClipIndex - 1;
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClipIndex],
                    TimeSpan.FromMilliseconds(300));
            }
            else if (keyboradState.IsKeyDown(Keys.Right) && lastKeyboradState.IsKeyUp(Keys.Right))
            {
                activeAnimationClipIndex = (activeAnimationClipIndex + 1) % skinnedModel.AnimationClips.Count;
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClipIndex],
                    TimeSpan.FromMilliseconds(300));
            }

            // 'Enter' enable/disable animation interpolation
            if (keyboradState.IsKeyDown(Keys.Enter) && lastKeyboradState.IsKeyUp(Keys.Enter))
            {
                enableInterpolation = !enableInterpolation;

                if (enableInterpolation)
                {
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Spherical;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }
                else
                {
                    animationController.TranslationInterpolation = InterpolationMode.None;
                    animationController.OrientationInterpolation = InterpolationMode.None;
                    animationController.ScaleInterpolation = InterpolationMode.None;
                }
            }
            
            // 'Tab' changes the current animated model
            if (keyboradState.IsKeyDown(Keys.Tab) && lastKeyboradState.IsKeyUp(Keys.Tab))
            {
                activeSkinnedModelIndex = (activeSkinnedModelIndex + 1) % skinnedModelFiles.Length;
                activeAnimationClipIndex = 0;
                LoadSkinnedModel();
            }

            lastKeyboradState = keyboradState;

            #endregion

            // Update the animation according to the elapsed time
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Gray);
            graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            graphics.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            graphics.GraphicsDevice.BlendState = BlendState.Opaque;

            skinnedModel.World = Matrix.Identity;
            skinnedModel.View = cameraView;
            skinnedModel.Projection = cameraProjection;
            skinnedModel.BoneTransforms = animationController.SkinnedBoneTransforms;
            skinnedModel.DiffuseColor = new Vector3(0.8f);
            skinnedModel.SpecularColor = new Vector3(0.3f);
            skinnedModel.SpecularPower = 8;

            // OPTIONAL - Configure lights
            skinnedModel.AmbientLightColor = new Vector3(0.1f);
            skinnedModel.LightEnabled = true;
            skinnedModel.EnabledLights = EnabledLights.One;
            skinnedModel.PointLights[0].Color = Vector3.One;
            skinnedModel.PointLights[0].Position = new Vector3(100);

            skinnedModel.Draw(); 
            
            Draw2D();

            base.Draw(gameTime);
        }

        #region Draw 2D Stuff
        private void Draw2D()
        {
            spriteBatch.Begin();

            spriteBatch.DrawString(spriteFont, "Use [LEFT/RIGHT] to Cross Fade Blend with a new animation clip.",
                new Vector2(10, 5), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [UP/DOWN] to change the animation speed.",
                new Vector2(10, 22), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [ENTER] to enable/disable animation interpolation.",
                new Vector2(10, 39), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [TAB] to change the animated model.",
                new Vector2(10, 56), Color.White);

            spriteBatch.DrawString(spriteFont, copyrightMessages[activeSkinnedModelIndex],
                new Vector2(560, 580), Color.White);

            spriteBatch.End();

        }
        #endregion

        #region Entry Point
        static void Main(string[] args)
        {
            using (XNAnimationSample game = new XNAnimationSample())
            {
                game.Run();
            }
        }
        #endregion
    }
}